/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package tools.costTools;

import com.openbrew.openbrewrf.Administration;
import com.openbrew.openbrewrf.Beer;
import com.openbrew.openbrewrf.BoilHops;
import com.openbrew.openbrewrf.CommonBrewSupplies;
import com.openbrew.openbrewrf.CostSettings;
import com.openbrew.openbrewrf.DryHops;
import com.openbrew.openbrewrf.FinishedGoodsInventory;
import com.openbrew.openbrewrf.InventoryUsage;
import com.openbrew.openbrewrf.LabProcess;
import com.openbrew.openbrewrf.LabType;
import com.openbrew.openbrewrf.MaltBill;
import com.openbrew.openbrewrf.PackageType;
import com.openbrew.openbrewrf.ProcessSteps;
import com.openbrew.openbrewrf.RecipeAdditives;
import com.openbrew.openbrewrf.Recipes;
import com.openbrew.openbrewrf.SelectedSalt;
import com.openbrew.openbrewrf.Supplies;
import com.openbrew.openbrewrf.VesselCleaningProcedure;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import service.costCalculator.CostCalcResults;
import service.enumerations.ProcessCategory;
import service.enumerations.RateType;
import service.enumerations.Units;

/**
 *
 * @author chapmand
 */
public class BeerCostTools {
    /***************************************************************************
     * Database search functions.
     **************************************************************************/
    private Beer getBeer(final EntityManager en, final int beerID){
        TypedQuery<Beer> query = en.createQuery(
            "SELECT b FROM Beer b WHERE b.bId = ?1"
            , Beer.class);
        query.setParameter(1, beerID);
        Beer beer = query.getSingleResult();
        return beer;
    }
    
    private Recipes getRecipe(final EntityManager en, final int recipeID){
        TypedQuery<Recipes> query = en.createQuery(
            "SELECT r FROM Recipes r WHERE r.rid = ?1"
            , Recipes.class);
        query.setParameter(1, recipeID);
        Recipes recipe = query.getSingleResult();
        return recipe;
    }
    
    private LabProcess getLabProcess(final EntityManager en, final int labID){
        TypedQuery<LabProcess> query = en.createQuery(
            "SELECT l FROM LabProcess l WHERE l.lpid = ?1"
            , LabProcess.class);
        query.setParameter(1, labID);
        LabProcess lp = query.getSingleResult();
        return lp;
    }
    
    private Supplies getSupply(final EntityManager en, final int supplyID){
        TypedQuery<Supplies> query = en.createQuery(
            "SELECT s FROM Supplies s WHERE s.supID = ?1"
            , Supplies.class);
        query.setParameter(1, supplyID);
        Supplies supply = query.getSingleResult();
        return supply;
    }
    
    private Administration getAdmin(final EntityManager en){
        TypedQuery<Administration> query = en.createQuery(
            "SELECT a FROM Administration a WHERE a.aNum = '1'", Administration.class);
        Administration admin = query.getSingleResult();
        return admin;
    }
    
    private CostSettings getCostSettings(final EntityManager en){
        TypedQuery<CostSettings> query = en.createQuery(
            "SELECT c FROM CostSettings c WHERE c.csid = '1'", CostSettings.class);
        CostSettings cs = query.getSingleResult();
        return cs;
    }
    
    private VesselCleaningProcedure getProcedure(final EntityManager en, final int procedureID){
        TypedQuery<VesselCleaningProcedure> query = en.createQuery(
            "SELECT v FROM VesselCleaningProcedure v WHERE v.vcpid = ?1"
            , VesselCleaningProcedure.class);
        query.setParameter(1, procedureID);
        VesselCleaningProcedure vcp = query.getSingleResult();
        return vcp;
    }
    
    private ProcessSteps getProcessStep(final EntityManager en, final int stepID){
        TypedQuery<ProcessSteps> query = en.createQuery(
            "SELECT p FROM ProcessSteps p WHERE p.psid = ?1"
            , ProcessSteps.class);
        query.setParameter(1, stepID);
        ProcessSteps ps = query.getSingleResult();
        return ps;
    }
    
    private PackageType getPackageType(final EntityManager en, final int pkgID){
        TypedQuery<PackageType> query = en.createQuery(
            "SELECT p FROM PackageType p WHERE p.packageTypeID = ?1"
            , PackageType.class);
        query.setParameter(1, pkgID);
        PackageType pt = query.getSingleResult();
        return pt;
    }
    
    private FinishedGoodsInventory getFGI(final EntityManager en, final int fgiID){
        TypedQuery<FinishedGoodsInventory> query = en.createQuery(
            "SELECT f FROM FinishedGoodsInventory f WHERE f.fgiid = ?1"
            , FinishedGoodsInventory.class);
        query.setParameter(1, fgiID);
        FinishedGoodsInventory fgi = query.getSingleResult();
        return fgi;
    }
    
    /***************************************************************************
     * getBeerCost Function - function to grab beer cost.
     * @param en
     * @param beerID
     * @return 
     **************************************************************************/
    public double getBeerCost(final EntityManager en, final int beerID){
        double cost = 0;
        if(beerID != 0){
            final Beer beer = getBeer(en, beerID);
            cost = calcRecipeCost(en, beer.getRecipeID());
        }
        return cost;
    }
    
    /***************************************************************************
     * getRecipeCost Function - function to grab beer cost.
     * @param en
     * @param recipeID
     * @return 
     **************************************************************************/
    public double getRecipeCost(final EntityManager en, final int recipeID){
        double cost = 0;
        if(recipeID != 0){
            final Recipes recipe = getRecipe(en, recipeID);
            cost = calcRecipeCost(en, recipe);
        }
        return cost;
    }
    
    /***************************************************************************
     * getRecipeCost Function - function to grab beer cost.
     * @param en
     * @param recipeID
     * @return 
     **************************************************************************/
    private double calcRecipeCost(final EntityManager en, final Recipes recipe){
        double cost = 0;
        if(recipe.getMaltBillCollection()!=null){
            for(final MaltBill mb : recipe.getMaltBillCollection()){
                cost += (double)(mb.getMaltLbs() * mb.getMaltID().getMaltCost());
            }
        }
        if(recipe.getBoilHopCollection()!=null){
            for(final BoilHops bh : recipe.getBoilHopCollection()){
                cost += (double)(bh.getBoilHopLbs() * bh.getHopID().getHopCost());
            }
        }
        if(recipe.getDryHopCollection()!=null){
            for(final DryHops dh : recipe.getDryHopCollection()){
                cost += (double)(dh.getDryHopLbs() * dh.getHopID().getHopCost());
            }
        }
        if(recipe.getAdditiveCollection()!=null){
            for(final RecipeAdditives ra : recipe.getAdditiveCollection()){
                final Supplies supply = getSupply(en, ra.getSupply().getSupID());
                final int unitFrom = Units.getUnitFromShortDescription(ra.getAdditiveUnits());
                final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                //final float newWeight = Units.convertUnits(unitFrom, unitTo, ra.getAdditiveLbs());
                final float newWeight = Units.convertUnits(unitFrom, unitTo, (ra.getAdditiveLbs()+(ra.getAdditiveLbs()*(supply.getAttrition()/100))));
                cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
            }
        }
        if(recipe.getYeastName()!=null){
            cost += (double)(recipe.getYeastName().getYeastCost()/recipe.getYeastName().getMaxGen());
        }
        if(recipe.getRecipeSaltID()!=null && recipe.getRecipeSaltID().getSelectedSaltCollection()!=null){
            for(final SelectedSalt ss : recipe.getRecipeSaltID().getSelectedSaltCollection()){
                if(ss.getWaterSaltID().getSupID()!=null){
                    final Supplies supply = ss.getWaterSaltID().getSupID();
                    final int unitFrom = Units.MASS_G.getUnit();
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    //final float newWeight = Units.convertUnits(unitFrom, unitTo, ss.getSelectedSaltGrams());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (ss.getSelectedSaltGrams()+(ss.getSelectedSaltGrams()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
        }
        final Administration admin = getAdmin(en);
        if(admin.getCbsCollection()!=null){
            for(final CommonBrewSupplies cbs : admin.getCbsCollection()){
                final Supplies supply = getSupply(en, cbs.getSupplyID().getSupID());
                final int unitFrom = Units.getUnitFromShortDescription(cbs.getUnits());
                final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                final float newWeight = Units.convertUnits(unitFrom, unitTo, cbs.getAmount());
                cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
            }
        }
        final CostSettings cs = getCostSettings(en);
        double pctIncrease = cs.getPctMaterialUpcharge()/(double)100;
        cost = cost + (cost * pctIncrease);
        return cost;
    }
    
    /***************************************************************************
     * getLabCost Function
     * @param en
     * @param labID
     * @param bbl  - Barrels being brewed
     * @param days  - Duration of days
     * @return 
     **************************************************************************/
    public double getLabCost(final EntityManager en, final int labID
            , final double bbl, final int days){
        double cost = 0;
        if(labID != 0){
            final LabProcess lp = getLabProcess(en, labID);
            
            //First Calculate Labor Costs
            final CostSettings cs = getCostSettings(en);
            final double laborCost = (double)lp.getTaskMen()*(double)lp.getTaskMinutes()
                    *((double)cs.getAvgEmpPay()/(double)60);
            
            RateType rt = RateType.getType(lp.getRateType());
            if(rt == RateType.FLAT_RATE){
                cost += laborCost;
            } else if (rt == RateType.PER_BBL){
                cost += laborCost*bbl;
            } else if (rt == RateType.PER_DAY){
                cost += laborCost*(double)days;
            }
            
            //Now Calculate material cost.
            if(lp.getInvUsageCollection()!=null){
                for(final InventoryUsage iu : lp.getInvUsageCollection()){
                    final Supplies supply = getSupply(en, iu.getSupplyID().getSupID());
                    final int unitFrom = Units.getUnitFromShortDescription(iu.getUnits());
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (iu.getAmount()+(iu.getAmount()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getProcedureCost Function
     * @param en
     * @param procedureID
     * @param bbl
     * @param days
     * @return 
     **************************************************************************/
    public double getProcedureCost(final EntityManager en, final int procedureID
            , final double bbl, final int days){
        double cost = 0;
        if(procedureID != 0){
            final VesselCleaningProcedure vcp = getProcedure(en, procedureID);
            
            //First Calculate Labor Costs
            final CostSettings cs = getCostSettings(en);
            final double laborCost = (double)vcp.getTaskMen()*(double)vcp.getTaskMinutes()
                    *((double)cs.getAvgEmpPay()/(double)60);
            
            RateType rt = RateType.getType(vcp.getRateType());
            if(rt == RateType.FLAT_RATE){
                cost += laborCost;
            } else if (rt == RateType.PER_BBL){
                cost += laborCost*bbl;
            } else if (rt == RateType.PER_DAY){
                cost += laborCost*(double)days;
            } else if (rt == RateType.PER_UNIT){
                cost += laborCost;
            }
            
            if(vcp.getInvUsageCollection()!=null){
                for(final InventoryUsage iu : vcp.getInvUsageCollection()){
                    final Supplies supply = getSupply(en, iu.getSupplyID().getSupID());
                    final int unitFrom = Units.getUnitFromShortDescription(iu.getUnits());
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (iu.getAmount()+(iu.getAmount()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getProcessStepsCost - Function searches through labs, processes, etc
     * assigned to a specific step.
     * @param en
     * @param processStepID
     * @param bbl
     * @param days
     * @return 
     **************************************************************************/
    public double getProcessStepsCost(final EntityManager en, final int processStepID
            , final double bbl, final int days){
        double cost = 0;
        if(processStepID != 0){
            final ProcessSteps ps = getProcessStep(en, processStepID);
            if(ps.getVcpCollection()!=null){
                for(final VesselCleaningProcedure vcp : ps.getVcpCollection()){
                    cost += getProcedureCost(en, vcp.getVcpid(), bbl, days);
                }
            }
            if(ps.getLabCollection()!=null){
                for(final LabType lt : ps.getLabCollection()){
                    if(lt.getLabProcess()!=null){
                        cost += getLabCost(en, lt.getLabProcess().getLpid(), bbl, days);
                    }
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getPackageTypeCost - Get Package Type Costs.
     * @param en
     * @param pkgID
     * @param bbl
     * @return 
     **************************************************************************/
    public double getPackageTypeCost(final EntityManager en, final int pkgID, final double bbl){
        double cost = 0;
        if(pkgID != 0){
            final PackageType pt = getPackageType(en, pkgID);
            if(pt.getInvUsageCollection()!=null){
                for(final InventoryUsage iu : pt.getInvUsageCollection()){
                    final Supplies supply = getSupply(en, iu.getSupplyID().getSupID());
                    final int unitFrom = Units.getUnitFromShortDescription(iu.getUnits());
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (iu.getAmount()+(iu.getAmount()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
            if(pt.getLabCollection()!=null){
                for(final LabType lt : pt.getLabCollection()){
                    if(lt.getLabProcess()!=null){
                        cost += getLabCost(en, lt.getLabProcess().getLpid(), bbl, 1);
                    }
                }
            }
            if(pt.getVcpCollection()!=null){
                for(final VesselCleaningProcedure vcp : pt.getVcpCollection()){
                    cost += getProcedureCost(en, vcp.getVcpid(), bbl, 1);
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getPackageTypeCost - This function only calculates the material cost of a 
     * package.
     **************************************************************************/
    private double getFGIMaterialCost(final EntityManager en, final int fgiID, final double bbl){
        double cost = 0;
        if(fgiID != 0){
            final FinishedGoodsInventory fgi = getFGI(en, fgiID);
            if(fgi.getInvUsageCollection()!=null){
                for(final InventoryUsage iu : fgi.getInvUsageCollection()){
                    final Supplies supply = getSupply(en, iu.getSupplyID().getSupID());
                    final int unitFrom = Units.getUnitFromShortDescription(iu.getUnits());
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (iu.getAmount()+(iu.getAmount()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
            if(fgi.getFGIPackageID()!=null && fgi.getFGIPackageID().getInvUsageCollection()!=null){
                for(final InventoryUsage iu : fgi.getFGIPackageID().getInvUsageCollection()){
                    final Supplies supply = getSupply(en, iu.getSupplyID().getSupID());
                    final int unitFrom = Units.getUnitFromShortDescription(iu.getUnits());
                    final int unitTo = Units.getUnitFromShortDescription(supply.getSupUnits());
                    final float newWeight = Units.convertUnits(unitFrom, unitTo, (iu.getAmount()+(iu.getAmount()*(supply.getAttrition()/100))));
                    cost += (double)(newWeight * (supply.getSupPrice()/supply.getSupQtyPer()));
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getFGIBatchCost - This function only calculates the material cost of a 
     * package.
     **************************************************************************/
    private double getFGIBatchCost(final EntityManager en, final int fgiID, final double bbl){
        double cost = 0;
        if(fgiID != 0){
            final FinishedGoodsInventory fgi = getFGI(en, fgiID);
            if(fgi.getFGIPackageID()!=null && fgi.getFGIPackageID().getLabCollection()!=null){
                for(final LabType lt : fgi.getFGIPackageID().getLabCollection()){
                    if(lt.getLabProcess()!=null){
                        cost += getLabCost(en, lt.getLabProcess().getLpid(), bbl, 1);
                    }
                }
            }
            if(fgi.getFGIPackageID()!=null && fgi.getFGIPackageID().getVcpCollection()!=null){
                for(final VesselCleaningProcedure vcp : fgi.getFGIPackageID().getVcpCollection()){
                    if(vcp.getRateType() != RateType.PER_UNIT.getType()){
                        cost += getProcedureCost(en, vcp.getVcpid(), bbl, 1);
                    }
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * getFGIBatchCost - This function only calculates the material cost of a 
     * package.
     **************************************************************************/
    private double getFGIPerUnitProcedureCost(final EntityManager en, final int fgiID){
        double cost = 0;
        if(fgiID != 0){
            final FinishedGoodsInventory fgi = getFGI(en, fgiID);
            if(fgi.getFGIPackageID()!=null && fgi.getFGIPackageID().getVcpCollection()!=null){
                for(final VesselCleaningProcedure vcp : fgi.getFGIPackageID().getVcpCollection()){
                    if(vcp.getRateType() == RateType.PER_UNIT.getType()){
                        cost += getProcedureCost(en, vcp.getVcpid(), 1, 1);
                    }
                }
            }
        }
        return cost;
    }
    
    /***************************************************************************
     * runFGICalc - calculates detailed report based on FGI ID.
     * @param en
     * @param fgiID
     * @param bbl
     * @return 
     **************************************************************************/
    public CostCalcResults runFGICalc(final EntityManager en, final int fgiID, final double bbl){
        CostCalcResults ccr = new CostCalcResults();
        final Administration admin = getAdmin(en);
        final CostSettings cs = getCostSettings(en);
        final FinishedGoodsInventory fgi = getFGI(en, fgiID);
        final Beer beer = getBeer(en, fgi.getFGIBeerID().getbId());
        
        /***********************************************************************
         * Start With recipe and overhead calculations
         **********************************************************************/
        //Set number of notes based on batch size.
        final double numbatches = (double)bbl / (double)admin.getSystemSize();
        final double num = Math.ceil(numbatches);
        
        //Set cost of recipe ingredients.
        ccr.setRecipeCost(getBeerCost(en, fgi.getFGIBeerID().getbId())*num);
        
        //Set cost of Water.
        ccr.setWaterCost((double)cs.getAvgWaterPerBBL()*(double)cs.getWaterCost()*(double)bbl);
        
        //Set cost of overhead.
        ccr.setOverheadCost((double)cs.getOverheadPerBBL()*(double)bbl);
        
        /***********************************************************************
         * Now calculate brew day items.
         * Run this for submitted barrel count and 1 day.
         **********************************************************************/
        ccr.setBrewDayCost(getProcessStepsCost(en, ProcessCategory.BREWING.getCategory(), bbl, 1));
        
        /***********************************************************************
         * Now calculate Primary Fermentation items.
         * Run this for submitted barrel count and beers primary day count.
         **********************************************************************/
        ccr.setPrimaryFermCost(getProcessStepsCost(en, ProcessCategory.PRIMARY_TO_CRASH.getCategory(), bbl, beer.getBeerFermDays()));
        
        /***********************************************************************
         * Now calculate Secondary Fermentation items.
         * Run this for submitted barrel count and beers secondary day count.
         **********************************************************************/
        ccr.setSecondaryFermCost(getProcessStepsCost(en, ProcessCategory.CRASH_TO_LAGER.getCategory(), bbl, beer.getBeerSecondDays()));
        
        /***********************************************************************
         * Now Calculate fine based on beer setup (i.e. is fine set?)
         * Run this for submitted barrel count and 1 day count.
         **********************************************************************/
        if(beer.getBeerFine()){
            ccr.setFineCost(getProcessStepsCost(en, ProcessCategory.LAGER_VIA_FINE.getCategory(), bbl, 1));
        }
        
        /***********************************************************************
         * Now Calculate fine based on beer setup (i.e. is fine set?)
         * Run this for submitted barrel count and 1 day count.
         **********************************************************************/
        if(beer.getBeerFilter()){
            ccr.setFilterCost(getProcessStepsCost(en, ProcessCategory.LAGER_VIA_FILTER.getCategory(), bbl, 1));
            ccr.setXferCost(0);
        } else {
            ccr.setFilterCost(0);
            ccr.setXferCost(getProcessStepsCost(en, ProcessCategory.LAGER_VIA_TRANSFER.getCategory(), bbl, 1));
        }
        
        //Now we have all of the cost for the batch except packaging.
        //We need to separate out all per unit items and add this to the packaging.
        //Then we need to distil this down to per unit cost, add on per unit labor and
        //per unit supplies.
        
        /***********************************************************************
         * Now Calculate cost based on actual FGI packaging.
         * First find all items used in packaging that are flat rate, per bbl, or
         * per min.  These will be added into the total cost of batch.
         **********************************************************************/
        //Set the package batch cost
        ccr.setPkgBatchCost(getFGIBatchCost(en, fgi.getFgiid(),bbl));
        //Set the total batch cost, this includes all items that had to go into the
        //beer before it is devided into individual package units.
        ccr.setTotalBatchCost();
        //Now we total up the individual package cost per unit.
        ccr.setPkgMaterialCost(getFGIMaterialCost(en, fgi.getFgiid(), bbl));
        ccr.setPkgUnitProcedureCost(getFGIPerUnitProcedureCost(en, fgi.getFgiid()));
        //Now subtotal the unit cost.
        ccr.setPkgUnitTotal();
        
        /***********************************************************************
         * At this point all items are caclulated now we just need to display it
         * As a per unit cost.
         * To do this, we simply calculate how many itms are in the batch, divide
         * the total batch cost by this number, add the per unit cost and display
         * it.
         **********************************************************************/
        //First find out how many units fit in the batch.
        final double units = bbl / ((double)fgi.getFGIPackageID().getPackageTypeVol() * (double)fgi.getFGIPackageID().getNumItems());
        ccr.setUnitsInBatch(units);
        final double totalBatchCost = ccr.getTotalBatchCost();
        final double batchPricePerUnit = totalBatchCost / units;
        ccr.setUnitBatchCost(batchPricePerUnit);
        final double totalUnitCost = batchPricePerUnit + ccr.getPkgUnitTotal();
        ccr.setTotalUnitCost(totalUnitCost);
        
        //Return the class.
        return ccr;
    }
}
